﻿using Identity_JWT_WebAPI.Dao.Model;
using Identity_JWT_WebAPI.Register.JWT;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;

namespace Identity_JWT_WebAPI.Controllers
{
    /// <summary>
    /// JWT一般会设置一到两个小时过期，过期后需要重新获取JWT，如果快要过期又使用到JWT就存在续期处理的问题：
    ///     a.获取新令牌： 后端使用刷新周期，例如是过期时间的2倍， 在这期间就算JWT过期，也返回相应的处理，并生成新的Token 放到Head中。前端就需要每次都检测Head中的特定Key有没有值，或有没有特定的Key
    ///     b.双Token模式： 登录成功后，返回两个Token, 一个是JWT，每次请求都要用， 一个是刷新Token, 只有当JWT过期后，通过特定的API传递这个刷新的Token获取新的JWT。 一般刷新的 Token会放到redis中，也会有过期时间 ， 当刷新的Token过期，那用户就需要重新登录了JWT一般会设置一到两个小时过期，过期后需要重新获取JWT，如果快要过期又使用到JWT就存在续期处理的问题：
    ///     a.获取新令牌： 后端使用刷新周期，例如是过期时间的2倍， 在这期间就算JWT过期，也返回相应的处理，并生成新的Token 放到Head中。前端就需要每次都检测Head中的特定Key有没有值，或有没有特定的Key
    ///     b.双Token模式： 登录成功后，返回两个Token, 一个是JWT，每次请求都要用， 一个是刷新Token, 只有当JWT过期后，通过特定的API传递这个刷新的Token获取新的JWT。 一般刷新的 Token会放到redis中，也会有过期时间 ， 当刷新的Token过期，那用户就需要重新登录了
    /// 
    /// </summary>
    [Authorize]
    [Route("api/[controller]/[action]")]
    [ApiController]
    public class JWTController : ControllerBase
    {
        private readonly UserManager<User> userManager;
        private readonly RoleManager<Role> roleManager;
        private readonly IOptionsSnapshot<JWTOptions> jwtOptions;

        public JWTController(UserManager<User> userManager, RoleManager<Role> roleManager, IOptionsSnapshot<JWTOptions> jwtOptions)
        {
            this.userManager = userManager;
            this.roleManager = roleManager;
            this.jwtOptions = jwtOptions;
        }


        /// <summary>
        /// 登录，成功返回JWT
        /// </summary>
        /// <param name="email"></param>
        /// <param name="pwd"></param>
        /// <returns></returns>
        [AllowAnonymous]
        [HttpGet]
        public async Task<ActionResult<string>> login(string email, string pwd)
        {
            User user = await userManager.FindByEmailAsync(email);
            if (user is null) return BadRequest("Email 不存在");
            if (await userManager.CheckPasswordAsync(user, pwd))
            {
                IList<string> roles = await userManager.GetRolesAsync(user);
                var claims = new List<Claim>();
                foreach (var role in roles)
                {
                    claims.Add(new Claim(ClaimTypes.Role, role));
                }
                claims.Add(new Claim(ClaimTypes.Email,user.Email));
                claims.Add(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()));
                claims.Add(new Claim("refresh", (jwtOptions.Value.ExpireSeconds * 3).ToString()));//刷新的过期时间
               
                //生成JWT,并返回
                DateTime expires = DateTime.UtcNow.AddSeconds(jwtOptions.Value.ExpireSeconds); //过期时间
                byte[] secBytes = Encoding.UTF8.GetBytes(jwtOptions.Value.SigningKey);
                var secKey = new SymmetricSecurityKey(secBytes);
                var credentials = new SigningCredentials(secKey, SecurityAlgorithms.HmacSha256);
                var tokenDescriptor = new JwtSecurityToken(claims: claims, expires: expires, signingCredentials: credentials);
                return new JwtSecurityTokenHandler().WriteToken(tokenDescriptor);

            }
            else
            {
                return BadRequest("登录失败");
            }
        }



        /// <summary>
        /// 只有登录后才可以访问该Action
        /// 并通过以下方式拿到Claim中的数据
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        public ActionResult<string> GetUseInfo()
        {
            string id = this.User.FindFirstValue(ClaimTypes.NameIdentifier);
            string email = this.User.FindFirstValue(ClaimTypes.Email);

            string expired =this.User.FindFirstValue("exp");
            DateTime dateTime = GetTime(expired);

            IEnumerable<Claim> claims = this.User.FindAll(ClaimTypes.Role);
            
            return Ok($"id={id},Email={email},roleNames ={claims.First().Value}, Expired={dateTime}");
        }


        /// <summary>
        /// 时间戳转成时间类型
        /// </summary>
        /// <param name="timeStamp"></param>
        /// <returns></returns>
        private DateTime GetTime(string timeStamp)
        {
            DateTime dtStart = TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1));
            long lTime = long.Parse(timeStamp + "0000000");
            TimeSpan toNow = new TimeSpan(lTime);
            return dtStart.Add(toNow);
        }

        /// <summary>
        /// 设置新的Token
        /// </summary>
        /// <returns></returns>
        [HttpPost]
        public ActionResult<string> ReturnNewJWT() 
        {
            this.Response.Headers.Add("Access-Control-Expose-Headers", "newToken"); //需要设置这个属性，newToken才能在Head中我哥哥他
            this.Response.Headers.Add("newToken", "ThisToken");
            this.Response.Headers.ContentType = "application/json;charset=utf-8";
            return "";
        }
    }
}
